﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.Linq.Mapping;
using System.Globalization;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using LZMA;

namespace IntraVision.Web
{
    public static class RequestHelper
    {
        public static int InitializeFromRequest<T>(this T obj, HttpRequest req, string requestPrefix, string requestPostfix)
        {
            int fcnt = 0;
            var props = (from prop in typeof(T).GetProperties() where prop.CanRead && prop.CanWrite && prop.GetCustomAttributes(typeof(AssociationAttribute), true).Length == 0 select prop);
            foreach (PropertyInfo pi in props)
            {
                ColumnAttribute dbcolumn = null;
                if (pi.GetCustomAttributes(typeof(ColumnAttribute), true).Length > 0)
                    dbcolumn = pi.GetCustomAttributes(typeof(ColumnAttribute), true)[0] as ColumnAttribute;
                //Dont load db generated columns
                if (dbcolumn != null && dbcolumn.IsDbGenerated) continue;
                string key = requestPrefix + pi.Name + requestPostfix;

                #region Отдельный код для чекбоксов
                if (pi.PropertyType == typeof(bool?) && req[key] == null)
                {
                    if (pi.GetValue(obj, null) == null) continue;
                    pi.SetValue(obj, (object)false, null);
                    fcnt++;
                    continue;
                }
                if (pi.PropertyType == typeof(bool) && req[key] == null)
                {
                    pi.SetValue(obj, (object)false, null);
                    fcnt++;
                    continue;
                }
                #endregion

                #region Отдельный код для файлов
                if (pi.PropertyType == typeof(System.Data.Linq.Binary) && req.Files[key] != null && req.Files[key].ContentLength > 0)
                {
                    byte[] data = new byte[req.Files[key].ContentLength];
                    req.Files[key].InputStream.Read(data, 0, req.Files[key].ContentLength);
                    pi.SetValue(obj, (object)new System.Data.Linq.Binary(data), null);
                    continue;
                }
                #endregion

                //String.IsNullOrEmpty cannot be used here because value can be empty.
                if (req[key] != null)
                {
                    try
                    {
                        object oldvalue = RequestHelper.ChangeType(pi.GetValue(obj, null), pi.PropertyType);
                        //If request is empty, trying to set value to null
                        if (string.IsNullOrEmpty(req[key]))
                        {
                            if (oldvalue == null || (pi.PropertyType == typeof(string) && (string)oldvalue == string.Empty)) continue;

                            pi.SetValue(obj, null, null);
                            fcnt++;
                            continue;
                        }
                        string val = req[key];
                        object newvalue = null;

                        //Если тип - дробное число, заменим точку на запятую
                        if (pi.PropertyType == typeof(Decimal) || pi.PropertyType == typeof(Double) || pi.PropertyType == typeof(Decimal?) || pi.PropertyType == typeof(Double?))
                            val = val.Replace('.', ',');
                        //Если тип - дата, распарсим ее по правилам русской культуры (12.02.2010)
                        if (pi.PropertyType == typeof(DateTime) || pi.PropertyType == typeof(DateTime?))
                            newvalue = DateTime.Parse(val, new CultureInfo("ru-RU").DateTimeFormat);
                        else
                            newvalue = RequestHelper.ChangeType(val, pi.PropertyType);

                        //If PropertyType is date and value is MinValue, set value to null
                        if (pi.PropertyType == typeof(DateTime) && (DateTime)newvalue == DateTime.MinValue)
                        {
                            pi.SetValue(obj, null, null);
                            fcnt++;
                            continue;
                        }
                        if (!newvalue.Equals(oldvalue))
                        {
                            pi.SetValue(obj, newvalue, null);
                            fcnt++;
                        }
                    }
                    catch { }
                }
            }
            return fcnt;
        }

        public static int InitializeFromRequest<T>(HttpRequest req, T obj, string requestPrefix, string requestPostfix)
        {
            return obj.InitializeFromRequest(req, requestPrefix, requestPostfix);
        }

        public static int InitializeFromRequest<T>(HttpRequest req, T obj)
        {
            return obj.InitializeFromRequest<T>(req, string.Empty, string.Empty);
        }
        
        /// <summary>
        /// Returns array of int from given string of identifiers separated by comma
        /// </summary>
        /// <param name="idstr">List of identifiers, e.g. 1,5,6,9</param>
        /// <returns>Array of int</returns>
        public static int[] GetIdsFromString(string idstr)
        {
            if (string.IsNullOrEmpty(idstr)) return new int[0];
            string[] ids = idstr.Split(',');
            if (ids.Length == 0) return new int[0];
            int[] result = new int[ids.Length];

            for (int i = 0; i < ids.Length; i++)
            {
                char[] idc = ids[i].ToCharArray();
                if (idc.Length == 0) { result[i] = 0; continue; }
                int s = idc.Length - 1;
                for (int c = idc.Length - 1; c >= 0; c--)
                {
                    if (Char.IsDigit(idc[c])) s = c;
                    else break;
                }
                if (!Int32.TryParse(ids[i].Substring(s), out result[i])) return new int[0];
            }
            return result;
        }

        /// <summary>
        /// Returns an Object with the specified Type and whose value is equivalent to the specified object.
        /// </summary>
        /// <param name="value">An Object that implements the IConvertible interface.</param>
        /// <param name="conversionType">The Type to which value is to be converted.</param>
        /// <returns>An object whose Type is conversionType (or conversionType's underlying type if conversionType
        /// is Nullable&lt;&gt;) and whose value is equivalent to value. -or- a null reference, if value is a null
        /// reference and conversionType is not a value type.</returns>
        /// <remarks>
        /// This method exists as a workaround to System.Convert.ChangeType(Object, Type) which does not handle
        /// nullables as of version 2.0 (2.0.50727.42) of the .NET Framework. The idea is that this method will
        /// be deleted once Convert.ChangeType is updated in a future version of the .NET Framework to handle
        /// nullable types, so we want this to behave as closely to Convert.ChangeType as possible.
        /// This method was written by Peter Johnson at:
        /// http://aspalliance.com/author.aspx?uId=1026.
        /// </remarks>
        public static object ChangeType(object value, Type conversionType)
        {
            // Note: This if block was taken from Convert.ChangeType as is, and is needed here since we're
            // checking properties on conversionType below.
            if (conversionType == null)
            {
                throw new ArgumentNullException("conversionType");
            } // end if

            // If it's not a nullable type, just pass through the parameters to Convert.ChangeType

            if (conversionType.IsGenericType &&
              conversionType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
            {
                // It's a nullable type, so instead of calling Convert.ChangeType directly which would throw a
                // InvalidCastException (per http://weblogs.asp.net/pjohnson/archive/2006/02/07/437631.aspx),
                // determine what the underlying type is
                // If it's null, it won't convert to the underlying type, but that's fine since nulls don't really
                // have a type--so just return null
                // Note: We only do this check if we're converting to a nullable type, since doing it outside
                // would diverge from Convert.ChangeType's behavior, which throws an InvalidCastException if
                // value is null and conversionType is a value type.
                if (value == null)
                {
                    return null;
                } // end if

                // It's a nullable type, and not null, so that means it can be converted to its underlying type,
                // so overwrite the passed-in conversion type with this underlying type
                System.ComponentModel.NullableConverter nullableConverter = new System.ComponentModel.NullableConverter(conversionType);
                conversionType = nullableConverter.UnderlyingType;
            } // end if

            // Now that we've guaranteed conversionType is something Convert.ChangeType can handle (i.e. not a
            // nullable type), pass the call on to Convert.ChangeType
            return Convert.ChangeType(value, conversionType);
        }

        public static String MD5Encode(String source)
        {
            if (source == null || source.Length == 0)
                return "";

            Encoding enc = new ASCIIEncoding();
            MD5 md5 = new MD5CryptoServiceProvider();
            byte[] bToConvert = md5.ComputeHash(enc.GetBytes(source));

            string sConverted = "";
            for (int i = 0; i < 16; i++)
                sConverted += String.Format(CultureInfo.InvariantCulture, "{0:x02}", bToConvert[i]);
            return sConverted;
        }

        public static System.Data.Linq.Binary GetPostedFileContent(HttpPostedFile file, string setting)
        {
            System.Data.Linq.Binary result;

            if (file == null || file.ContentLength == 0)
                return new byte[0];

            //Проверка на максимальный размер
            int maxFileSize;
            Int32.TryParse(Settings.GetFromDatabase(setting), out maxFileSize);
            if (maxFileSize > 0 && file.ContentLength > maxFileSize * 1000)
                throw new Exception(String.Format(CultureInfo.InvariantCulture, "Вы пытаетесь загрузить слишком большой файл. Максимальный размер загружаемого файла равен {0} килобайт.", maxFileSize));

            //Считывание файла в поток байт
            var data = new byte[file.ContentLength];
            try{ 
                file.InputStream.Read(data, 0, file.ContentLength); 
            }
            catch (Exception e){ 
                throw new Exception("Ошибка загрузки файла", e); 
            }

            //Сжатие файла
            try{ 
                result = new System.Data.Linq.Binary(Compressor.CompressBytes(data)); 
            }
            catch{ 
                result = new System.Data.Linq.Binary(data); 
            }

            return result;
        }

        public static System.Data.Linq.Binary GetPostedFileContent(HttpPostedFile file)
        {
            return GetPostedFileContent(file, "maxfilesize");
        }
    }
}
